#include "stdafx.h"
#include "Viewer.h"
#include "..\Render\Render.h"
#include "Model.h"
#include "Scene.h"
#include "Material.h"
//
//StreamedMesh::StreamedMesh(const char* pFilename) {
//
//	if(!pFilename)
//		return;
//
//	FILE* pFile = fopen(pFilename, "rb");
//	if(!pFile)
//		return;
//
//	u_int shaderFeature = 0;
//
//
//	fread(&m_TriangleCount, sizeof(u_int), 1, pFile);
//	fread(&shaderFeature, sizeof(u_int), 1, pFile);
//	
//	u_int formatSize = GetVertexFormatSize(m_VertexFormat);
//	u_int bufferSize = m_TriangleCount * 3 * formatSize;
//
//	m_pTriangleData = new byte [bufferSize];
//	
//	fread(m_pTriangleData, sizeof(byte), bufferSize, pFile);
//	fclose(pFile);
//
//	if(!Render::Singleton()->CreateVertexIndicieBuffer(bufferSize, (LPVOID)m_pTriangleData, m_VertexFormat, &m_pVertexBuffer, &m_pIndexBuffer)) {
//
//		m_pVertexBuffer = NULL;
//		m_pIndexBuffer = NULL;
//	}
//
//	D3DXMatrixIdentity(&m_World);
//	m_Shader = NULL;
//	m_pSOBuffers[2] = NULL;
//	m_pStreamOutBufferB = NULL;
//	if(!Render::Singleton()->CreateShader("default_shader.fx", m_VertexFormat, shaderFeature, &m_Shader)) {
//
//		m_Shader = NULL;
//	}
//	
//	/*
//	Create an aditional buffer for tesselation if required
//	*/
//	if(m_VertexFormat & VERTEX_DEFORMATION) {
//	
//		if(!Render::Singleton()->CreateVertexBuffer(bufferSize, true, &m_pSOBuffers[2])) {
//
//			MessageBox(0,0,0,0);
//		}
//
//		if(!Render::Singleton()->CreateVertexBuffer(bufferSize, true, &m_pStreamOutBufferB)) {
//
//			MessageBox(0,0,0,0);
//		}
//
//	} else {
//
//		if(m_pSOBuffers[2]) {
//
//			Render::Singleton()->ReleaseResource(&m_pSOBuffers[2]);
//			m_pSOBuffers[2] = NULL;
//		}
//		if(m_pStreamOutBufferB) {
//
//			Render::Singleton()->ReleaseResource(&m_pStreamOutBufferB);
//			m_pStreamOutBufferB = NULL;
//		}
//	}
//	memcpy(&m_ID, pFilename, sizeof(tCharBuffer)); 
//}

/**
	Construtor
	
	@author Dacre Denny
	
*/
StreamedMesh::StreamedMesh()
{	
	ZeroMemory(&m_ID, sizeof(tCharBuffer));
	D3DXMatrixIdentity(&m_World);

	m_Deformation = false;
	m_DeformationScale = 0.0f;

	m_Shader = NULL;
	m_hDecalMaterial = NULL;
	m_hNormalMaterial = NULL;

	m_pTriangleData = NULL;
	m_TriangleCount = 0;
		
	m_pVertexBuffer = NULL;
	m_pIndexBuffer	= NULL;
	m_pSOBuffers[0] = NULL;
	m_pSOBuffers[1] = NULL;
}


/**
	Construtor
	
	@author Dacre Denny
	@param p_ID pointer to character buffer that initializes this instances internal ID
	
*/
StreamedMesh::StreamedMesh(const std::string& ID)
{	
	memcpy(&m_ID, &ID, sizeof(tCharBuffer)); 
	D3DXMatrixIdentity(&m_World);

	m_Deformation = false;
	m_DeformationScale = 0.0f;

	m_Shader = NULL;
	m_hDecalMaterial = NULL;
	m_hNormalMaterial = NULL;

	m_pTriangleData = NULL;
	m_TriangleCount = 0;
		
	m_pVertexBuffer = NULL;
	m_pIndexBuffer	= NULL;
	m_pSOBuffers[0] = NULL;
	m_pSOBuffers[1] = NULL;
}

void StreamedMesh::SetTransform(tMatrix4 *pTransform) {

	memcpy(&m_World, pTransform, sizeof(D3DXMATRIX));
}

void StreamedMesh::SetShader(const std::string& shaderFilename, const int shaderFeatureSet, const int vertexFormat) {

	if(!Render::Singleton()->CreateShader(shaderFilename, vertexFormat, shaderFeatureSet, &m_Shader)) {

		Render::GetDefaultShader(1, &m_Shader);
	}
}

bool CharBuffer_Equals(const tCharBuffer* pBuuffer, const char* pString)
{
	if(!pString)
		return false;

	return (0 == strncmp(pBuuffer->buffer, pString, 32));
}

/**
	Enables external updating of materials used by this mesh instance. The updates should be reflected
	immediatly in the subsequent rendering frame
	
	@author Dacre Denny
	@param pHeader pointer to header of the material to direct the datas application
	@param pMaterial pointer to the material pHeader being applied to the update
	
*/
void StreamedMesh::UpdateMaterial(HeaderStructs::tSurfaceTextureHeader* pHeader, byte* pMaterial) {

	try {

		if(!pHeader || !pMaterial)
			throw tException();

		/*
		If material header is targeting decal channel
		*/
		if(CharBuffer_Equals(&pHeader->channel, "decal")) {

			if(!m_hDecalMaterial && !Materials::Create(&m_hDecalMaterial))
				throw tException();

			if(!Materials::UpdateMaterials(pHeader->type, pMaterial, m_hDecalMaterial))
				throw tException();
		}
		
		/*
		If material header is targeting normal channel
		*/
		if(CharBuffer_Equals(&pHeader->channel, "normal")) {

			if(!m_hNormalMaterial && !Materials::Create(&m_hNormalMaterial))
				throw tException();

			if(!Materials::UpdateMaterials(pHeader->type, pMaterial, m_hNormalMaterial))
				throw tException();
		}

	}catch(tException e) {

		std::string message;
		tException::GetMessage(&e, message);
	}
}

/**
	Enables external updating of geometry that represents this mesh instance. The updates should be reflected
	immediatly in the subsequent rendering frame
	
	@author Dacre Denny
	@param pHeader pointer to header of the geoemtry data
	@param pTriangleData pointer to the triangle data being applied to the update
	
*/
void StreamedMesh::UpdateGeometry(HeaderStructs::tGeometryHeader *pHeader, byte* pTriangleData) {

	Render::tResource* pSOBuffers[2] = { NULL };

	try {

		if(!pHeader || !pTriangleData)
			throw tException();

		uint vertexFormat = pHeader->vertexFormat;
		uint vertexSize = GetVertexFormatSize(vertexFormat);
		uint bufferSize = pHeader->triangleCount * 3 * vertexSize;
		if(bufferSize == 0)
			throw tException("Invalid vertex format/buffer size for object:%s", std::string(m_ID.buffer).c_str());
		
		/*
		Update the geometry of the object as required
		*/
		if(!Render::Singleton()->CreateVertexIndicieBuffer(bufferSize, (LPVOID)pTriangleData, vertexFormat,	&m_pVertexBuffer, &m_pIndexBuffer))
			throw tException("Failed to update geometry for object:%s", std::string(m_ID.buffer).c_str());
		m_TriangleCount = pHeader->triangleCount;

		/*
		Create or update shader if required
		*/
		if(!m_Shader) {

			if(!Render::GetDefaultShader(vertexFormat, &m_Shader))
				throw tException("Failed to create shader for object:%s", std::string(m_ID.buffer).c_str());
		}
		else if(vertexFormat != m_Shader->inputFormat) {
			
			if(!Render::Singleton()->CreateShader(m_Shader->filename, vertexFormat, m_Shader->features, &m_Shader, true))
				throw tException("Failed to match shader format for object:%s", std::string(m_ID.buffer).c_str());
		}

		/*
		Create aditional buffers for tesselation if required
		*/
		if(vertexFormat & VERTEX_DEFORMATION) {
		
			if(!Render::Singleton()->CreateVertexBuffer(bufferSize, true, &pSOBuffers[0]) ||
				!Render::Singleton()->CreateVertexBuffer(bufferSize, true, &pSOBuffers[1]))
				throw tException("Failed to create stream out ping-pong buffers for object:%s", std::string(m_ID.buffer).c_str());
			
		} else {

			Render::Singleton()->ReleaseResource(&m_pSOBuffers[0]);
			Render::Singleton()->ReleaseResource(&m_pSOBuffers[1]);
			
			m_pSOBuffers[0] = NULL;
			m_pSOBuffers[1] = NULL;
		}
		
		Scene::WriteToDisk(this, std::string(m_ID.buffer));
	}
	catch(tException e) {

		std::string message;
		tException::GetMessage(&e, message);
		m_TriangleCount = 0;
		Render::Singleton()->ReleaseResource(&m_pVertexBuffer);
		Render::Singleton()->ReleaseResource(&m_pIndexBuffer);
		Render::Singleton()->ReleaseShader(m_Shader);
	}
}

/**
	Retrieves the number of child instance meshes to this mesh.
	
	@author Dacre Denny

	@return the number of child mesh instances to this mesh
*/
u_int StreamedMesh::ChildInstancingCount() {

	return m_IPPairs.size();
}

/**
	Attempts to aquire the mesh instancing geometry and associated procedural of this mesh via
	the provided index.
	
	@author Dacre Denny
	@param index index of the child that is being aquired
	@param StreamedMesh pointer to child mesh if it exists
	@param tProceduralData* pointer to child procedural if it exists
	
	@return true if the function is successful otherwise false
*/
bool StreamedMesh::GetChildInstance(uint index, StreamedMesh** pInstanceChildOut, tProceduralData* pProceduralOut) {

	/*if(index > ChildInstancingCount() - 1)
		return false;

	for(std::vector<tInstancingProcedural>::iterator i = m_IPPairs.begin(); i != m_IPPairs.end(); i++) {

		if(index == 0) {
			pInstanceChildOut	= &(i->m_pInstanceChild);
			pProceduralOut		= &(i->m_Procedural);
			return true;
		}
		index--;
	}*/

	return false;
}

/**
	Attempts to automate the instancing/placement of geometry in a procedural manner. This
	mesh instance acts as the 'parent surface' to constrain the scattering of 'child geometry'
	across it's surfaces. This scattering is dictated by the specified procedural pHeader. Calling
	this function appends the child mesh to this mesh so that the child mesh is distributed
	accordingly.

	A child mesh can only be distributed by one procedural.
	
	@author Dacre Denny
	@param pInstanceChild the child geometry to be scattered across this mesh instance
	@param pHeader the pHeader structure containing the procedural's header information
	@param pSurfaceTextureData the pHeader of the procedural

	@return true if the function is successful otherwise false
*/
bool StreamedMesh::AddChildInstancing(StreamedMesh* pInstanceChild, HeaderStructs::tSurfaceTextureHeader* pHeader, byte* pSurfaceTextureData) {

	if(!pInstanceChild)
		return false;

	for(std::vector<tInstancingProcedural>::iterator i = m_IPPairs.begin(); i != m_IPPairs.end(); i++) {

		//Permit replacing a meshes procedural function
		if(i->m_pInstanceChild == pInstanceChild) {
			if(pHeader)
				i->proceduralHeader = *pHeader;
			i->pProceduralData = pSurfaceTextureData;
			return true;
		}
	}

	Render::tResource *pInstanceBuffer = NULL;
	if(!Render::Singleton()->CreateVertexBuffer(0, true, &pInstanceBuffer))
		return false;

	tInstancingProcedural IPPair;
	IPPair.m_pInstanceBuffer = pInstanceBuffer;
	IPPair.m_pInstanceChild = pInstanceChild;
	IPPair.pProceduralData = pSurfaceTextureData;
	if(pHeader)
		IPPair.proceduralHeader = *pHeader;
	m_IPPairs.push_back(IPPair);

	return true;
}

/**
	Calling this function removed the specified child mesh from this meshes 'instancing children'
	meshes. The side effect of this function is that the child mesh will no longer be scattered across
	this mesh, if it was previously a child of this mesh.
	
	@author Dacre Denny
	@param pInstanceChild the child geometry to be removed from this meshes instancing children

	@return true if the function is successful otherwise false
*/
bool StreamedMesh::RemoveChildInstancing(StreamedMesh* pInstanceChild) {

	if(!pInstanceChild || m_IPPairs.size() == 0)
		return false;

	for(std::vector<tInstancingProcedural>::iterator i = m_IPPairs.begin(); i != m_IPPairs.end(); i++) {

		if(i->m_pInstanceChild == pInstanceChild) {
			
			if(!Render::Singleton()->ReleaseResource(&i->m_pInstanceBuffer))
				return false;
			if(i->pProceduralData)
				delete [] i->pProceduralData;

			m_IPPairs.erase(i);
			return true;
		}
	}

	return false;
}

void StreamedMesh::DrawInstancedChildren() {

	for(std::vector<tInstancingProcedural>::iterator i = m_IPPairs.begin(); i != m_IPPairs.end(); i++) {

		StreamedMesh* pChildMesh = i->m_pInstanceChild;
		UINT instances = 0;

		if(!Render::Singleton()->StreamOutBuffer(this, i->m_pInstanceBuffer, &instances))
			continue;

		pChildMesh->Draw(NULL, false, i->m_pInstanceBuffer, instances);
	}
	
}

/**
	Calling this function renders the specified mesh. Optional parameters for instancing this
	mesh according to a specified instance buffer is also possible.
	
	@author Dacre Denny
	@param pShader the shader to override this geometry with, otherwise use it's own
	@param pInstanceChild the child geometry to be rendered across the mesh
	@param instanceCount the number of instance children to be rendered
*/
void StreamedMesh::Draw(Render::tShader* pShader, bool deformationOverride, Render::tResource* pInstanceBuffer, UINT instanceCount) {

	if(!m_pVertexBuffer || (!pShader && !m_Shader))
		return;

	bool deformation = (deformationOverride && m_Deformation) ? true : false;
	Render::tShader* pTargetShader = pShader ? pShader : m_Shader;

	bool isInstancing = pInstanceBuffer && (instanceCount > 0);
	UINT passes = 0;
	if(Render::Singleton()->BeginShader(pTargetShader, isInstancing, &passes)) {

		Materials::ApplyToShader(pTargetShader, "decal", m_hDecalMaterial);
		Materials::ApplyToShader(pTargetShader, "normal", m_hNormalMaterial);

		UINT stride = GetVertexFormatSize(pTargetShader->inputFormat);
		const bool isIndexed = (m_pIndexBuffer != NULL);

		for(UINT i = 0; i < passes; i++)
			if(Render::Singleton()->BeginShaderPass(i)) {

				UINT strides[2] = {stride, isInstancing ? Render::INSTANCE_STRIDE : 0};

				if(deformation) {

					const bool alternate = ((i % 2) == 0);
					Render::tResource* pStreamTo = alternate ? m_pSOBuffers[0] : m_pSOBuffers[1];
					Render::tResource* pStreamFrom[2] = { (i == 0) ? m_pVertexBuffer : ((!alternate) ? m_pSOBuffers[0] : m_pSOBuffers[1]), isInstancing ? pInstanceBuffer : NULL };
					

					Render::Singleton()->BindStreamOutBuffer(pStreamTo, stride);
					Render::Singleton()->BindVertexBuffer(pStreamFrom, strides, pInstanceBuffer ? 2 : 1);
				}
				else {

					Render::tResource* pBinding[2] = { m_pVertexBuffer, isInstancing ? pInstanceBuffer : NULL };
					Render::Singleton()->BindVertexBuffer(pBinding, strides, pInstanceBuffer ? 2 : 1);
				}
		
				if(isIndexed) {

					Render::Singleton()->BindIndexBuffer(m_pIndexBuffer, sizeof(uint));
					Render::Singleton()->DrawIndexedTriangleList(m_TriangleCount, isInstancing ? &instanceCount : NULL);
				}
				else {

					Render::Singleton()->DrawTriangleList(m_TriangleCount, isInstancing ? &instanceCount : NULL);
				}
					
				Render::Singleton()->EndShaderPass();
			}

		Render::Singleton()->EndShader();
	}

	if(!(pTargetShader->features & INSTANCE_STREAMING))
		DrawInstancedChildren();
}

void StreamedMesh::Release() {

	Render::Singleton()->ReleaseResource(&m_pVertexBuffer);
	Render::Singleton()->ReleaseResource(&m_pIndexBuffer);
	Render::Singleton()->ReleaseResource(&m_pSOBuffers[0]);
	Render::Singleton()->ReleaseResource(&m_pSOBuffers[1]);

	Render::Singleton()->ReleaseShader(m_Shader);

	D3DXMatrixIdentity(&m_World);
	if(m_pTriangleData) {
		delete [] m_pTriangleData;
		m_pTriangleData = NULL;
	}
	m_TriangleCount = 0;
}